#include "device/conv_dma.h"
#include "mem/sram.h"
#include "mem/dram.h"
#include "mem/mem_if.h"
#include <memory>
#include <vector>

ConvDMA::ConvDMA(std::shared_ptr<MemInterface> In, std::shared_ptr<MemInterface> Out){
    InMemPtr = std::move(In);
    OutMemPtr = std::move(Out);
    convDmaEn = false;
    for(int i=0;i<PORT_NUM;i++)
    {
        vgrEn[i] = false;
        vgrRaddr_l[i] = 0;
        vgrRaddr_h[i] = 0;
        dramAddrStep[i] = 0;
        burstDataLen[i] = 0;
        burstDataTimes[i] = 0;
        writeDataLen[i] = 0;
        ramWaddr[i] = 0;
        ramWaddr_offset[i] = 0;
        ramWaddrStep[i] = 0;
        mode[i] = 0;
        scale[i] = 0;
        padZero[i] = 0;
        irqStatus[i] = 0;
        irqMask[i] = 0;
    }
    DMABuffer = new u8[2*1024*1024];
}

ConvDMA::~ConvDMA(){
    delete [] DMABuffer;
}

void ConvDMA::Run(){
    if(!convDmaEn) return;
    for(int i=0;i<PORT_NUM;i++){
        if(!vgrEn[i]) continue;
        u64 vgrRaddr = ((u64)vgrRaddr_h[i] << 4) + vgrRaddr_l[i];
        u64 sramByteAddr = ((u64)ramWaddr[i] << 5) | (u64)ramWaddr_offset[i];
        switch(mode[i]){
        case 0x0:
        {
            u64 dram_addr_tmp = vgrRaddr;
            u64 sram_addr_tmp = sramByteAddr;
            u64 read_len = burstDataLen[i];
            CHECK(read_len == writeDataLen[i]);
            for(u16 j=0;j<burstDataTimes[i];j++){
                InMemPtr->read_mem(DMABuffer,
                        dram_addr_tmp, read_len);
                OutMemPtr->write_mem(sram_addr_tmp,
                        DMABuffer, writeDataLen[i]);
                dram_addr_tmp += dramAddrStep[i];
                sram_addr_tmp += ramWaddrStep[i];
            }
            break;
        }
        case 0x1:
        {
            u64 dram_addr_tmp = vgrRaddr;
            u64 len = burstDataLen[i];
            u64 times = burstDataTimes[i];
            CHECK(len <= 16);
            CHECK(times <= 16);
            u8 tmp[16];
            for(u32 j=0;j<times;j++){
                u64 sram_addr_tmp = sramByteAddr + j;
                InMemPtr->read_mem(&tmp, dram_addr_tmp, len);
                for(u32 k=0;k<len;k++){
                    OutMemPtr->write_mem(sram_addr_tmp, tmp + k, 1);
                    sram_addr_tmp += ramWaddrStep[i];
                }
                dram_addr_tmp += dramAddrStep[i];
            }
            break;
        }
        case 0x2:
        {
            u64 dram_addr_tmp = vgrRaddr;
            u64 sram_addr_tmp = sramByteAddr;
            u64 read_len = burstDataLen[i];
            CHECK(scale[i] <= 5);
            u64 padInter = scale[i];
            u64 write_len = burstDataLen[i] + padInter * (burstDataLen[i] - 1);
            CHECK(write_len == writeDataLen[i]);
            CHECK(writeDataLen[i] * padInter == padZero[i]);
            for(u16 j=0;j<burstDataTimes[i];j++){
                InMemPtr->read_mem(DMABuffer,
                        dram_addr_tmp, read_len);
                u32 pad_zero_num = padInter;
                for(int k=read_len-1;k>0;k--)
                {
                    u64 pre_index = k;
                    s64 post_index = (pad_zero_num + 1) * k;
                    DMABuffer[post_index] = DMABuffer[pre_index];
                    for(u32 t=0;t<pad_zero_num;t++){
                        post_index--;
                        DMABuffer[post_index] = 0;
                    }
                }
                for(u32 t= 0; t<padZero[i]; t++)
                {
                    DMABuffer[write_len + t] = 0;
                }
                OutMemPtr->write_mem(sram_addr_tmp,
                        DMABuffer, write_len + padZero[i]);
                dram_addr_tmp += dramAddrStep[i];
                sram_addr_tmp += ramWaddrStep[i];
            }
            break;
        }
        case 0x4:
        {
            u64 dram_addr_tmp = vgrRaddr;
            u64 sram_addr_tmp = sramByteAddr;
            u64 read_len = burstDataLen[i];
            u32 scale_horizontal = scale[i] & 0x7;
            u32 scale_vertical = (scale[i] & 0x18) >> 3;
            CHECK(scale_horizontal <= 3);
            CHECK(scale_vertical <= 3);
            u64 write_len = burstDataLen[i] * (scale_horizontal + 1);
            CHECK(write_len == writeDataLen[i]);
            for(u16 j=0;j<burstDataTimes[i];j++){
                InMemPtr->read_mem(DMABuffer,
                        dram_addr_tmp, read_len);
                for(int k = read_len - 1; k >= 0; k--)
                {
                    u64 pre_index = k;
                    s64 post_index = (scale_horizontal + 1) * (k + 1) - 1;
                    for(;post_index >= (scale_horizontal + 1) * k; post_index--){
                        DMABuffer[post_index] = DMABuffer[pre_index];
                    }
                }
                for(u32 k = 0; k < scale_vertical + 1; k++){
                    OutMemPtr->write_mem(sram_addr_tmp, DMABuffer, write_len);
                    sram_addr_tmp += ramWaddrStep[i];
                }
                dram_addr_tmp += dramAddrStep[i];
            }
            break;
        }
        default:
            CHECK_MSG(false, "error mode in dma!");
        }
        irqStatus[i] = 1;
    }
    return;
}

